1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package com.google.common.io;
16
17 import static com.google.common.base.Preconditions.checkArgument;
18 import static com.google.common.base.Preconditions.checkNotNull;
19 import static com.google.common.base.Preconditions.checkPositionIndexes;
20 import static com.google.common.base.Preconditions.checkState;
21 import static com.google.common.io.GwtWorkarounds.asCharInput;
22 import static com.google.common.io.GwtWorkarounds.stringBuilderOutput;
23 import static com.google.common.math.IntMath.divide;
24 import static com.google.common.math.IntMath.log2;
25 import static java.math.RoundingMode.CEILING;
26 import static java.math.RoundingMode.FLOOR;
27 import static java.math.RoundingMode.UNNECESSARY;
28
29 import com.google.common.annotations.Beta;
30 import com.google.common.annotations.GwtCompatible;
31 import com.google.common.base.Ascii;
32 import com.google.common.base.CharMatcher;
33 import com.google.common.io.GwtWorkarounds.ByteInput;
34 import com.google.common.io.GwtWorkarounds.ByteOutput;
35 import com.google.common.io.GwtWorkarounds.CharInput;
36 import com.google.common.io.GwtWorkarounds.CharOutput;
37
38 import java.io.IOException;
39 import java.util.Arrays;
40
41 import javax.annotation.CheckReturnValue;
42 import javax.annotation.Nullable;
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123 @Beta
124 @GwtCompatible(emulated = true)
125 public abstract class BaseEncoding {
126
127
128 BaseEncoding() {}
129
130
131
132
133
134
135
136 public static final class DecodingException extends IOException {
137 DecodingException(String message) {
138 super(message);
139 }
140
141 DecodingException(Throwable cause) {
142 super(cause);
143 }
144 }
145
146
147
148
149 public String encode(byte[] bytes) {
150 return encode(checkNotNull(bytes), 0, bytes.length);
151 }
152
153
154
155
156
157 public final String encode(byte[] bytes, int off, int len) {
158 checkNotNull(bytes);
159 checkPositionIndexes(off, off + len, bytes.length);
160 CharOutput result = stringBuilderOutput(maxEncodedSize(len));
161 ByteOutput byteOutput = encodingStream(result);
162 try {
163 for (int i = 0; i < len; i++) {
164 byteOutput.write(bytes[off + i]);
165 }
166 byteOutput.close();
167 } catch (IOException impossible) {
168 throw new AssertionError("impossible");
169 }
170 return result.toString();
171 }
172
173
174
175 private static byte[] extract(byte[] result, int length) {
176 if (length == result.length) {
177 return result;
178 } else {
179 byte[] trunc = new byte[length];
180 System.arraycopy(result, 0, trunc, 0, length);
181 return trunc;
182 }
183 }
184
185
186
187
188
189
190
191
192 public final byte[] decode(CharSequence chars) {
193 try {
194 return decodeChecked(chars);
195 } catch (DecodingException badInput) {
196 throw new IllegalArgumentException(badInput);
197 }
198 }
199
200
201
202
203
204
205
206
207 final byte[] decodeChecked(CharSequence chars) throws DecodingException {
208 chars = padding().trimTrailingFrom(chars);
209 ByteInput decodedInput = decodingStream(asCharInput(chars));
210 byte[] tmp = new byte[maxDecodedSize(chars.length())];
211 int index = 0;
212 try {
213 for (int i = decodedInput.read(); i != -1; i = decodedInput.read()) {
214 tmp[index++] = (byte) i;
215 }
216 } catch (DecodingException badInput) {
217 throw badInput;
218 } catch (IOException impossible) {
219 throw new AssertionError(impossible);
220 }
221 return extract(tmp, index);
222 }
223
224
225
226 abstract int maxEncodedSize(int bytes);
227
228 abstract ByteOutput encodingStream(CharOutput charOutput);
229
230 abstract int maxDecodedSize(int chars);
231
232 abstract ByteInput decodingStream(CharInput charInput);
233
234 abstract CharMatcher padding();
235
236
237
238
239
240
241
242
243 @CheckReturnValue
244 public abstract BaseEncoding omitPadding();
245
246
247
248
249
250
251
252
253 @CheckReturnValue
254 public abstract BaseEncoding withPadChar(char padChar);
255
256
257
258
259
260
261
262
263
264
265 @CheckReturnValue
266 public abstract BaseEncoding withSeparator(String separator, int n);
267
268
269
270
271
272
273
274
275 @CheckReturnValue
276 public abstract BaseEncoding upperCase();
277
278
279
280
281
282
283
284
285 @CheckReturnValue
286 public abstract BaseEncoding lowerCase();
287
288 private static final BaseEncoding BASE64 = new StandardBaseEncoding(
289 "base64()", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", '=');
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304 public static BaseEncoding base64() {
305 return BASE64;
306 }
307
308 private static final BaseEncoding BASE64_URL = new StandardBaseEncoding(
309 "base64Url()", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", '=');
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325 public static BaseEncoding base64Url() {
326 return BASE64_URL;
327 }
328
329 private static final BaseEncoding BASE32 =
330 new StandardBaseEncoding("base32()", "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", '=');
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345 public static BaseEncoding base32() {
346 return BASE32;
347 }
348
349 private static final BaseEncoding BASE32_HEX =
350 new StandardBaseEncoding("base32Hex()", "0123456789ABCDEFGHIJKLMNOPQRSTUV", '=');
351
352
353
354
355
356
357
358
359
360
361
362
363
364 public static BaseEncoding base32Hex() {
365 return BASE32_HEX;
366 }
367
368 private static final BaseEncoding BASE16 =
369 new StandardBaseEncoding("base16()", "0123456789ABCDEF", null);
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385 public static BaseEncoding base16() {
386 return BASE16;
387 }
388
389 private static final class Alphabet extends CharMatcher {
390 private final String name;
391
392 private final char[] chars;
393 final int mask;
394 final int bitsPerChar;
395 final int charsPerChunk;
396 final int bytesPerChunk;
397 private final byte[] decodabet;
398 private final boolean[] validPadding;
399
400 Alphabet(String name, char[] chars) {
401 this.name = checkNotNull(name);
402 this.chars = checkNotNull(chars);
403 try {
404 this.bitsPerChar = log2(chars.length, UNNECESSARY);
405 } catch (ArithmeticException e) {
406 throw new IllegalArgumentException("Illegal alphabet length " + chars.length, e);
407 }
408
409
410
411
412
413 int gcd = Math.min(8, Integer.lowestOneBit(bitsPerChar));
414 this.charsPerChunk = 8 / gcd;
415 this.bytesPerChunk = bitsPerChar / gcd;
416
417 this.mask = chars.length - 1;
418
419 byte[] decodabet = new byte[Ascii.MAX + 1];
420 Arrays.fill(decodabet, (byte) -1);
421 for (int i = 0; i < chars.length; i++) {
422 char c = chars[i];
423 checkArgument(CharMatcher.ASCII.matches(c), "Non-ASCII character: %s", c);
424 checkArgument(decodabet[c] == -1, "Duplicate character: %s", c);
425 decodabet[c] = (byte) i;
426 }
427 this.decodabet = decodabet;
428
429 boolean[] validPadding = new boolean[charsPerChunk];
430 for (int i = 0; i < bytesPerChunk; i++) {
431 validPadding[divide(i * 8, bitsPerChar, CEILING)] = true;
432 }
433 this.validPadding = validPadding;
434 }
435
436 char encode(int bits) {
437 return chars[bits];
438 }
439
440 boolean isValidPaddingStartPosition(int index) {
441 return validPadding[index % charsPerChunk];
442 }
443
444 int decode(char ch) throws IOException {
445 if (ch > Ascii.MAX || decodabet[ch] == -1) {
446 throw new DecodingException("Unrecognized character: " + ch);
447 }
448 return decodabet[ch];
449 }
450
451 private boolean hasLowerCase() {
452 for (char c : chars) {
453 if (Ascii.isLowerCase(c)) {
454 return true;
455 }
456 }
457 return false;
458 }
459
460 private boolean hasUpperCase() {
461 for (char c : chars) {
462 if (Ascii.isUpperCase(c)) {
463 return true;
464 }
465 }
466 return false;
467 }
468
469 Alphabet upperCase() {
470 if (!hasLowerCase()) {
471 return this;
472 } else {
473 checkState(!hasUpperCase(), "Cannot call upperCase() on a mixed-case alphabet");
474 char[] upperCased = new char[chars.length];
475 for (int i = 0; i < chars.length; i++) {
476 upperCased[i] = Ascii.toUpperCase(chars[i]);
477 }
478 return new Alphabet(name + ".upperCase()", upperCased);
479 }
480 }
481
482 Alphabet lowerCase() {
483 if (!hasUpperCase()) {
484 return this;
485 } else {
486 checkState(!hasLowerCase(), "Cannot call lowerCase() on a mixed-case alphabet");
487 char[] lowerCased = new char[chars.length];
488 for (int i = 0; i < chars.length; i++) {
489 lowerCased[i] = Ascii.toLowerCase(chars[i]);
490 }
491 return new Alphabet(name + ".lowerCase()", lowerCased);
492 }
493 }
494
495 @Override
496 public boolean matches(char c) {
497 return CharMatcher.ASCII.matches(c) && decodabet[c] != -1;
498 }
499
500 @Override
501 public String toString() {
502 return name;
503 }
504 }
505
506 static final class StandardBaseEncoding extends BaseEncoding {
507
508 private final Alphabet alphabet;
509
510 @Nullable
511 private final Character paddingChar;
512
513 StandardBaseEncoding(String name, String alphabetChars, @Nullable Character paddingChar) {
514 this(new Alphabet(name, alphabetChars.toCharArray()), paddingChar);
515 }
516
517 StandardBaseEncoding(Alphabet alphabet, @Nullable Character paddingChar) {
518 this.alphabet = checkNotNull(alphabet);
519 checkArgument(paddingChar == null || !alphabet.matches(paddingChar),
520 "Padding character %s was already in alphabet", paddingChar);
521 this.paddingChar = paddingChar;
522 }
523
524 @Override
525 CharMatcher padding() {
526 return (paddingChar == null) ? CharMatcher.NONE : CharMatcher.is(paddingChar.charValue());
527 }
528
529 @Override
530 int maxEncodedSize(int bytes) {
531 return alphabet.charsPerChunk * divide(bytes, alphabet.bytesPerChunk, CEILING);
532 }
533
534 @Override
535 ByteOutput encodingStream(final CharOutput out) {
536 checkNotNull(out);
537 return new ByteOutput() {
538 int bitBuffer = 0;
539 int bitBufferLength = 0;
540 int writtenChars = 0;
541
542 @Override
543 public void write(byte b) throws IOException {
544 bitBuffer <<= 8;
545 bitBuffer |= b & 0xFF;
546 bitBufferLength += 8;
547 while (bitBufferLength >= alphabet.bitsPerChar) {
548 int charIndex = (bitBuffer >> (bitBufferLength - alphabet.bitsPerChar))
549 & alphabet.mask;
550 out.write(alphabet.encode(charIndex));
551 writtenChars++;
552 bitBufferLength -= alphabet.bitsPerChar;
553 }
554 }
555
556 @Override
557 public void flush() throws IOException {
558 out.flush();
559 }
560
561 @Override
562 public void close() throws IOException {
563 if (bitBufferLength > 0) {
564 int charIndex = (bitBuffer << (alphabet.bitsPerChar - bitBufferLength))
565 & alphabet.mask;
566 out.write(alphabet.encode(charIndex));
567 writtenChars++;
568 if (paddingChar != null) {
569 while (writtenChars % alphabet.charsPerChunk != 0) {
570 out.write(paddingChar.charValue());
571 writtenChars++;
572 }
573 }
574 }
575 out.close();
576 }
577 };
578 }
579
580 @Override
581 int maxDecodedSize(int chars) {
582 return (int) ((alphabet.bitsPerChar * (long) chars + 7L) / 8L);
583 }
584
585 @Override
586 ByteInput decodingStream(final CharInput reader) {
587 checkNotNull(reader);
588 return new ByteInput() {
589 int bitBuffer = 0;
590 int bitBufferLength = 0;
591 int readChars = 0;
592 boolean hitPadding = false;
593 final CharMatcher paddingMatcher = padding();
594
595 @Override
596 public int read() throws IOException {
597 while (true) {
598 int readChar = reader.read();
599 if (readChar == -1) {
600 if (!hitPadding && !alphabet.isValidPaddingStartPosition(readChars)) {
601 throw new DecodingException("Invalid input length " + readChars);
602 }
603 return -1;
604 }
605 readChars++;
606 char ch = (char) readChar;
607 if (paddingMatcher.matches(ch)) {
608 if (!hitPadding
609 && (readChars == 1 || !alphabet.isValidPaddingStartPosition(readChars - 1))) {
610 throw new DecodingException("Padding cannot start at index " + readChars);
611 }
612 hitPadding = true;
613 } else if (hitPadding) {
614 throw new DecodingException(
615 "Expected padding character but found '" + ch + "' at index " + readChars);
616 } else {
617 bitBuffer <<= alphabet.bitsPerChar;
618 bitBuffer |= alphabet.decode(ch);
619 bitBufferLength += alphabet.bitsPerChar;
620
621 if (bitBufferLength >= 8) {
622 bitBufferLength -= 8;
623 return (bitBuffer >> bitBufferLength) & 0xFF;
624 }
625 }
626 }
627 }
628
629 @Override
630 public void close() throws IOException {
631 reader.close();
632 }
633 };
634 }
635
636 @Override
637 public BaseEncoding omitPadding() {
638 return (paddingChar == null) ? this : new StandardBaseEncoding(alphabet, null);
639 }
640
641 @Override
642 public BaseEncoding withPadChar(char padChar) {
643 if (8 % alphabet.bitsPerChar == 0 ||
644 (paddingChar != null && paddingChar.charValue() == padChar)) {
645 return this;
646 } else {
647 return new StandardBaseEncoding(alphabet, padChar);
648 }
649 }
650
651 @Override
652 public BaseEncoding withSeparator(String separator, int afterEveryChars) {
653 checkNotNull(separator);
654 checkArgument(padding().or(alphabet).matchesNoneOf(separator),
655 "Separator cannot contain alphabet or padding characters");
656 return new SeparatedBaseEncoding(this, separator, afterEveryChars);
657 }
658
659 private transient BaseEncoding upperCase;
660 private transient BaseEncoding lowerCase;
661
662 @Override
663 public BaseEncoding upperCase() {
664 BaseEncoding result = upperCase;
665 if (result == null) {
666 Alphabet upper = alphabet.upperCase();
667 result = upperCase =
668 (upper == alphabet) ? this : new StandardBaseEncoding(upper, paddingChar);
669 }
670 return result;
671 }
672
673 @Override
674 public BaseEncoding lowerCase() {
675 BaseEncoding result = lowerCase;
676 if (result == null) {
677 Alphabet lower = alphabet.lowerCase();
678 result = lowerCase =
679 (lower == alphabet) ? this : new StandardBaseEncoding(lower, paddingChar);
680 }
681 return result;
682 }
683
684 @Override
685 public String toString() {
686 StringBuilder builder = new StringBuilder("BaseEncoding.");
687 builder.append(alphabet.toString());
688 if (8 % alphabet.bitsPerChar != 0) {
689 if (paddingChar == null) {
690 builder.append(".omitPadding()");
691 } else {
692 builder.append(".withPadChar(").append(paddingChar).append(')');
693 }
694 }
695 return builder.toString();
696 }
697 }
698
699 static CharInput ignoringInput(final CharInput delegate, final CharMatcher toIgnore) {
700 checkNotNull(delegate);
701 checkNotNull(toIgnore);
702 return new CharInput() {
703 @Override
704 public int read() throws IOException {
705 int readChar;
706 do {
707 readChar = delegate.read();
708 } while (readChar != -1 && toIgnore.matches((char) readChar));
709 return readChar;
710 }
711
712 @Override
713 public void close() throws IOException {
714 delegate.close();
715 }
716 };
717 }
718
719 static CharOutput separatingOutput(
720 final CharOutput delegate, final String separator, final int afterEveryChars) {
721 checkNotNull(delegate);
722 checkNotNull(separator);
723 checkArgument(afterEveryChars > 0);
724 return new CharOutput() {
725 int charsUntilSeparator = afterEveryChars;
726
727 @Override
728 public void write(char c) throws IOException {
729 if (charsUntilSeparator == 0) {
730 for (int i = 0; i < separator.length(); i++) {
731 delegate.write(separator.charAt(i));
732 }
733 charsUntilSeparator = afterEveryChars;
734 }
735 delegate.write(c);
736 charsUntilSeparator--;
737 }
738
739 @Override
740 public void flush() throws IOException {
741 delegate.flush();
742 }
743
744 @Override
745 public void close() throws IOException {
746 delegate.close();
747 }
748 };
749 }
750
751 static final class SeparatedBaseEncoding extends BaseEncoding {
752 private final BaseEncoding delegate;
753 private final String separator;
754 private final int afterEveryChars;
755 private final CharMatcher separatorChars;
756
757 SeparatedBaseEncoding(BaseEncoding delegate, String separator, int afterEveryChars) {
758 this.delegate = checkNotNull(delegate);
759 this.separator = checkNotNull(separator);
760 this.afterEveryChars = afterEveryChars;
761 checkArgument(
762 afterEveryChars > 0, "Cannot add a separator after every %s chars", afterEveryChars);
763 this.separatorChars = CharMatcher.anyOf(separator).precomputed();
764 }
765
766 @Override
767 CharMatcher padding() {
768 return delegate.padding();
769 }
770
771 @Override
772 int maxEncodedSize(int bytes) {
773 int unseparatedSize = delegate.maxEncodedSize(bytes);
774 return unseparatedSize + separator.length()
775 * divide(Math.max(0, unseparatedSize - 1), afterEveryChars, FLOOR);
776 }
777
778 @Override
779 ByteOutput encodingStream(final CharOutput output) {
780 return delegate.encodingStream(separatingOutput(output, separator, afterEveryChars));
781 }
782
783 @Override
784 int maxDecodedSize(int chars) {
785 return delegate.maxDecodedSize(chars);
786 }
787
788 @Override
789 ByteInput decodingStream(final CharInput input) {
790 return delegate.decodingStream(ignoringInput(input, separatorChars));
791 }
792
793 @Override
794 public BaseEncoding omitPadding() {
795 return delegate.omitPadding().withSeparator(separator, afterEveryChars);
796 }
797
798 @Override
799 public BaseEncoding withPadChar(char padChar) {
800 return delegate.withPadChar(padChar).withSeparator(separator, afterEveryChars);
801 }
802
803 @Override
804 public BaseEncoding withSeparator(String separator, int afterEveryChars) {
805 throw new UnsupportedOperationException("Already have a separator");
806 }
807
808 @Override
809 public BaseEncoding upperCase() {
810 return delegate.upperCase().withSeparator(separator, afterEveryChars);
811 }
812
813 @Override
814 public BaseEncoding lowerCase() {
815 return delegate.lowerCase().withSeparator(separator, afterEveryChars);
816 }
817
818 @Override
819 public String toString() {
820 return delegate.toString() +
821 ".withSeparator(\"" + separator + "\", " + afterEveryChars + ")";
822 }
823 }
824 }